package cn.iocoder.yudao.module.infra.service.ocr;

import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.api.ocr.dto.OcrRecognizeRespDTO;
import cn.iocoder.yudao.module.infra.controller.admin.ocr.vo.OcrRecognizeRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.ocr.OcrConfigDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.ocr.OcrRecordDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
import cn.iocoder.yudao.module.infra.service.file.FileService;

import com.baidu.aip.ocr.AipOcr;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;

/**
 * OCR Recognition Service Implementation
 *
 * @author YuDao
 */
@Service
@Validated
@Slf4j
public class OcrServiceImpl implements OcrService {

    @Resource
    private OcrConfigService ocrConfigService;

    @Resource
    private OcrRecordService ocrRecordService;

    @Resource
    private FileService fileService;

    @Resource
    private FileMapper fileMapper;

    @Override
    public OcrRecognizeRespVO recognizeText(Long configId, Long fileId) {
        // Get OCR configuration
        OcrConfigDO config = ocrConfigService.getOcrConfig(configId);
        if (config == null) {
            throw exception(OCR_CONFIG_NOT_EXISTS);
        }

        // Get file information
        FileDO file = validateFileExists(fileId);

        // Determine OCR type and call corresponding method
        switch (config.getType()) {
            case "general_basic":
                return recognizeGeneralText(configId, fileId);
            case "accurate_basic":
                return recognizeGeneralText(configId, fileId);
            case "idcard":
                return recognizeIdCard(configId, fileId);
            case "business_license":
                return recognizeBusinessLicense(configId, fileId);
            case "bankcard":
                return recognizeBankCard(configId, fileId);
            case "driving_license":
                return recognizeDrivingLicense(configId, fileId);
            case "vehicle_license":
                return recognizeVehicleLicense(configId, fileId);
            default:
                throw exception(OCR_TYPE_NOT_SUPPORTED);
        }
    }

    @Override
    public OcrRecognizeRespVO recognizeGeneralText(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 1, "generalBasic");
    }

    @Override
    public OcrRecognizeRespVO recognizeIdCard(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 2, "idcard");
    }

    @Override
    public OcrRecognizeRespVO recognizeBusinessLicense(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 3, "businessLicense");
    }

    @Override
    public OcrRecognizeRespVO recognizeBankCard(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 4, "bankcard");
    }

    @Override
    public OcrRecognizeRespVO recognizeDrivingLicense(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 5, "drivingLicense");
    }

    @Override
    public OcrRecognizeRespVO recognizeVehicleLicense(Long configId, Long fileId) {
        return performOcrRecognition(configId, fileId, 6, "vehicleLicense");
    }

    private OcrRecognizeRespVO performOcrRecognition(Long configId, Long fileId, Integer type, String ocrMethod) {
        long startTime = System.currentTimeMillis();

        log.info("开始OCR识别 - configId: {}, fileId: {}, type: {}, method: {}", configId, fileId, type, ocrMethod);

        // Get OCR configuration
        OcrConfigDO config = ocrConfigService.getOcrConfig(configId);
        if (config == null) {
            log.error("OCR配置不存在 - configId: {}", configId);
            throw exception(OCR_CONFIG_NOT_EXISTS);
        }
        log.info("获取OCR配置成功 - configId: {}, type: {}, appId: {}", configId, config.getType(), config.getAppId());

        // Get file information
        FileDO file = validateFileExists(fileId);
        log.info("获取文件信息成功 - fileId: {}, fileName: {}, fileUrl: {}", fileId, file.getName(), file.getUrl());

        // Create OCR record
        OcrRecordDO record = new OcrRecordDO();
        record.setConfigId(configId);
        record.setFileId(fileId);
        record.setOcrType(type.toString());
        record.setFileName(file.getName());
        record.setFileUrl(file.getUrl());
        record.setStatus(0); // Initially set to failed

        try {
            // Initialize Baidu OCR client
            log.info("初始化百度OCR客户端 - appId: {}", config.getAppId());
            AipOcr client = new AipOcr(config.getAppId(), config.getApiKey(), config.getSecretKey());

            // Set connection timeout
            client.setConnectionTimeoutInMillis(2000);
            client.setSocketTimeoutInMillis(60000);
            log.info("设置OCR客户端超时参数完成");

            // Perform OCR recognition based on method
            log.info("开始调用百度OCR API - method: {}, imageUrl: {}", ocrMethod, file.getUrl());
            JSONObject result = callBaiduOcrApi(client, file.getUrl(), ocrMethod);
            log.info("百度OCR API调用成功 - 结果长度: {}", result != null ? result.toString().length() : 0);

            // Calculate processing time
            long processTime = System.currentTimeMillis() - startTime;

            // Parse OCR result and fill structured fields
            OcrRecognizeRespVO tempResponse = new OcrRecognizeRespVO();
            parseOcrResult(tempResponse, result, ocrMethod);

            // Update record with success result
            record.setRecognitionResult(result.toString());
            record.setRecognitionText(tempResponse.getText()); // 设置识别文本
            record.setConfidence(new BigDecimal(tempResponse.getConfidence())); // 设置置信度
            record.setStatus(1); // Success
            record.setProcessingTime((int) processTime);

            // Save record
            Long recordId = ocrRecordService.createOcrRecord(record);

            // Create response
            OcrRecognizeRespVO response = new OcrRecognizeRespVO();
            response.setRecordId(recordId);
            response.setStatus(1);
            response.setResult(result.toString());
            response.setProcessTime(processTime);

            // Parse OCR result and fill structured fields
            parseOcrResult(response, result, ocrMethod);

            return response;

        } catch (Exception e) {
            log.error("OCR识别失败 - fileId: {}, configId: {}, method: {}, error: {}", fileId, configId, ocrMethod,
                    e.getMessage(), e);

            // Calculate processing time
            long processTime = System.currentTimeMillis() - startTime;

            // Update record with error
            record.setErrorMessage(e.getMessage());
            record.setProcessingTime((int) processTime);

            // Save record
            Long recordId = ocrRecordService.createOcrRecord(record);

            // Create error response
            OcrRecognizeRespVO response = new OcrRecognizeRespVO();
            response.setRecordId(recordId);
            response.setStatus(0);
            response.setErrorMsg(e.getMessage());
            response.setProcessTime(processTime);

            return response;
        }
    }

    private JSONObject callBaiduOcrApi(AipOcr client, String imageUrl, String method) {
        HashMap<String, String> options = new HashMap<>();
        
        // 根据不同的OCR方法设置专用参数
        if ("idcard".equals(method)) {
            // 身份证识别专用参数
            options.put("detect_direction", "true");     // 检测图像朝向
            options.put("detect_risk", "true");         // 开启身份证风险类型检测
            options.put("detect_quality", "true");      // 开启身份证质量检测
            options.put("detect_photo", "true");        // 开启身份证照片检测
            options.put("detect_card", "true");         // 开启身份证边框检测
        } else {
            // 通用参数
            options.put("language_type", "CHN_ENG");
            options.put("detect_direction", "true");
            options.put("detect_language", "true");
        }
        
        // 所有识别都启用置信度返回
        options.put("probability", "true");

        try {
            // Download image from URL
            URL url = new URL(imageUrl);
            byte[] imageData;
            try (InputStream inputStream = url.openStream()) {
                imageData = readAllBytes(inputStream);
            }

            // Call different OCR APIs based on method
            switch (method) {
                case "generalBasic":
                    return client.basicGeneral(imageData, options);
                case "idcard":
                    // 身份证识别，明确指定正面
                    return client.idcard(imageData, "front", options);
                case "businessLicense":
                    return client.businessLicense(imageData, options);
                case "bankcard":
                    return client.bankcard(imageData, new HashMap<>());
                case "drivingLicense":
                    return client.drivingLicense(imageData, options);
                case "vehicleLicense":
                    return client.vehicleLicense(imageData, options);
                default:
                    return client.basicGeneral(imageData, options);
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to download image from URL: " + imageUrl, e);
        }
    }

    private byte[] getFileData(Long fileId) {
        FileDO file = validateFileExists(fileId);
        try {
            return fileService.getFileContent(file.getConfigId(), file.getPath());
        } catch (Exception e) {
            throw exception(OCR_API_CALL_FAIL);
        }
    }

    private FileDO validateFileExists(Long id) {
        FileDO fileDO = fileMapper.selectById(id);
        if (fileDO == null) {
            throw exception(FILE_NOT_EXISTS);
        }
        return fileDO;
    }

    /**
     * Parse OCR result JSON and fill structured fields
     */
    private void parseOcrResult(OcrRecognizeRespVO response, JSONObject result, String ocrMethod) {
        try {
            if (result == null) {
                return;
            }

            // Parse based on OCR method
            switch (ocrMethod) {
                case "generalBasic":
                    parseGeneralTextResult(response, result);
                    break;
                case "idcard":
                    parseIdCardResult(response, result);
                    break;
                case "businessLicense":
                case "bankcard":
                case "drivingLicense":
                case "vehicleLicense":
                    parseSpecialDocumentResult(response, result);
                    break;
                default:
                    parseGeneralTextResult(response, result);
                    break;
            }
        } catch (Exception e) {
            log.warn("解析OCR结果失败: {}", e.getMessage());
            // If parsing fails, at least set basic text content
            response.setText(result.toString());
            response.setConfidence(0.0);
        }
    }

    /**
     * Parse general text OCR result
     */
    private void parseGeneralTextResult(OcrRecognizeRespVO response, JSONObject result) {
        StringBuilder textBuilder = new StringBuilder();
        List<OcrRecognizeRespVO.WordResult> wordResults = new ArrayList<>();
        double totalConfidence = 0.0;
        int wordCount = 0;

        if (result.has("words_result")) {
            JSONArray wordsResult = result.getJSONArray("words_result");
            for (int i = 0; i < wordsResult.length(); i++) {
                JSONObject word = wordsResult.getJSONObject(i);
                String wordText = word.optString("words", "");

                if (!wordText.isEmpty()) {
                    textBuilder.append(wordText);
                    if (i < wordsResult.length() - 1) {
                        textBuilder.append("\n");
                    }

                    // Create word result
                    OcrRecognizeRespVO.WordResult wordResult = new OcrRecognizeRespVO.WordResult();
                    wordResult.setText(wordText);

                    // Get confidence if available
                    double confidence = word.optDouble("probability", 0.0);
                    if (confidence == 0.0 && word.has("probability")) {
                        JSONObject probObj = word.optJSONObject("probability");
                        if (probObj != null) {
                            confidence = probObj.optDouble("average", 0.0);
                        }
                    }
                    wordResult.setConfidence(confidence);
                    totalConfidence += confidence;
                    wordCount++;

                    // Get location if available
                    if (word.has("location")) {
                        JSONObject location = word.getJSONObject("location");
                        OcrRecognizeRespVO.Location loc = new OcrRecognizeRespVO.Location();
                        loc.setLeft(location.optInt("left", 0));
                        loc.setTop(location.optInt("top", 0));
                        loc.setWidth(location.optInt("width", 0));
                        loc.setHeight(location.optInt("height", 0));
                        wordResult.setLocation(loc);
                    }

                    wordResults.add(wordResult);
                }
            }
        }

        response.setText(textBuilder.toString());
        response.setConfidence(wordCount > 0 ? totalConfidence / wordCount : 0.0);
        response.setWords(wordResults);
    }

    /**
     * Parse ID card OCR result
     */
    private void parseIdCardResult(OcrRecognizeRespVO response, JSONObject result) {
        StringBuilder textBuilder = new StringBuilder();
        List<OcrRecognizeRespVO.WordResult> wordResults = new ArrayList<>();
        List<OcrRecognizeRespVO.FieldResult> fieldResults = new ArrayList<>();

        if (result.has("words_result")) {
            JSONObject wordsResult = result.getJSONObject("words_result");
            
            // 记录百度OCR返回的所有字段名用于调试
            log.info("百度OCR返回的所有字段名: {}", wordsResult.keySet());
            
            // 定义身份证字段映射：百度OCR返回的字段名 -> 标准化字段名
            Map<String, String> fieldMapping = new HashMap<>();
            fieldMapping.put("姓名", "姓名");
            fieldMapping.put("姓　名", "姓名");  // 全角空格
            fieldMapping.put("民族", "民族");
            fieldMapping.put("住址", "住址");
            fieldMapping.put("公民身份证号码", "公民身份证号码");
            fieldMapping.put("公民身份号码", "公民身份证号码");  // 可能的别名
            fieldMapping.put("身份证号码", "公民身份证号码");  // 可能的别名
            fieldMapping.put("身份证号", "公民身份证号码");    // 可能的别名
            fieldMapping.put("证件号码", "公民身份证号码");    // 可能的别名
            fieldMapping.put("号码", "公民身份证号码");       // 简写
            fieldMapping.put("身份证", "公民身份证号码");      // 简写
            fieldMapping.put("出生", "出生");
            fieldMapping.put("性别", "性别");
            fieldMapping.put("性　别", "性别");  // 全角空格
            
            // 标准化字段顺序
            String[] standardFields = { "姓名", "民族", "住址", "公民身份号码", "出生", "性别" };
            
            // 首先处理百度OCR返回的所有字段
            for (String baiduFieldName : wordsResult.keySet()) {
                JSONObject fieldObj = wordsResult.getJSONObject(baiduFieldName);
                String fieldText = fieldObj.optString("words", "");
                
                // 获取标准化的字段名
                String standardFieldName = fieldMapping.getOrDefault(baiduFieldName, baiduFieldName);
                
                log.info("处理字段: {} -> {}, 值: {}", baiduFieldName, standardFieldName, fieldText);
                
                // 创建字段结果
                OcrRecognizeRespVO.FieldResult fieldResult = new OcrRecognizeRespVO.FieldResult();
                fieldResult.setFieldName(standardFieldName);
                fieldResult.setFieldValue(fieldText); // 可能为空字符串
                fieldResult.setConfidence(fieldObj.optDouble("probability", 0.0));
                fieldResults.add(fieldResult);
                
                // 只有非空字段才添加到文本和words结果中
                if (!fieldText.isEmpty()) {
                    textBuilder.append(standardFieldName).append(": ").append(fieldText).append("\n");

                    // Create word result
                    OcrRecognizeRespVO.WordResult wordResult = new OcrRecognizeRespVO.WordResult();
                    wordResult.setText(standardFieldName + ": " + fieldText);
                    wordResult.setConfidence(fieldObj.optDouble("probability", 0.0));

                    // Get location if available
                    if (fieldObj.has("location")) {
                        JSONObject location = fieldObj.getJSONObject("location");
                        OcrRecognizeRespVO.Location loc = new OcrRecognizeRespVO.Location();
                        loc.setLeft(location.optInt("left", 0));
                        loc.setTop(location.optInt("top", 0));
                        loc.setWidth(location.optInt("width", 0));
                        loc.setHeight(location.optInt("height", 0));
                        wordResult.setLocation(loc);
                    }

                    wordResults.add(wordResult);
                }
            }
            
            // 确保所有标准字段都存在，缺失的用空值补充
            Set<String> existingFields = fieldResults.stream()
                    .map(OcrRecognizeRespVO.FieldResult::getFieldName)
                    .collect(Collectors.toSet());
                    
            for (String standardField : standardFields) {
                if (!existingFields.contains(standardField)) {
                    OcrRecognizeRespVO.FieldResult fieldResult = new OcrRecognizeRespVO.FieldResult();
                    fieldResult.setFieldName(standardField);
                    fieldResult.setFieldValue("");
                    fieldResult.setConfidence(0.0);
                    fieldResults.add(fieldResult);
                    log.info("补充缺失字段: {}, 值: 空", standardField);
                }
            }
        }

        response.setText(textBuilder.toString().trim());
        response.setConfidence(0.95); // Default confidence for ID card
        response.setWords(wordResults);
        response.setFields(fieldResults);
        
        // 检查是否需要备用解析（当身份证号码为空时）
        boolean needFallbackParsing = fieldResults.stream()
                .filter(f -> "公民身份证号码".equals(f.getFieldName()))
                .anyMatch(f -> f.getFieldValue() == null || f.getFieldValue().trim().isEmpty());
                
        if (needFallbackParsing) {
            log.warn("身份证号码字段为空，尝试备用解析机制");
            log.info("备用解析 - 原始OCR结果: {}", result != null ? result.toString() : "null");
            tryFallbackIdNumberExtraction(response, result, fieldResults);
        } else {
            // 即使字段不为空，也要验证身份证号码格式是否正确
            for (OcrRecognizeRespVO.FieldResult field : fieldResults) {
                if ("公民身份证号码".equals(field.getFieldName()) && 
                    field.getFieldValue() != null && !field.getFieldValue().trim().isEmpty()) {
                    String idNumber = field.getFieldValue().trim();
                    if (!isValidIdNumber(idNumber)) {
                        log.warn("身份证号码格式不正确: {}, 尝试重新提取", idNumber);
                        tryFallbackIdNumberExtraction(response, result, fieldResults);
                        break;
                    }
                }
            }
        }
        
        log.info("身份证OCR解析完成，字段数量: {}, 非空字段数量: {}", 
                fieldResults.size(), 
                fieldResults.stream().mapToInt(f -> f.getFieldValue().isEmpty() ? 0 : 1).sum());
    }
    
    /**
     * 备用身份证号码提取机制
     * 当百度OCR无法识别身份证号码时，使用通用文本识别提取
     */
    private void tryFallbackIdNumberExtraction(OcrRecognizeRespVO response, JSONObject result, List<OcrRecognizeRespVO.FieldResult> fieldResults) {
        try {
            // 尝试从全文本中提取身份证号码
            String fullText = extractFullTextFromResult(result);
            if (fullText != null && !fullText.trim().isEmpty()) {
                log.info("提取到全文本内容，长度: {}", fullText.length());
                
                // 使用正则表达式提取身份证号码
                String extractedIdNumber = extractIdNumberFromText(fullText);
                if (extractedIdNumber != null && !extractedIdNumber.trim().isEmpty()) {
                    log.info("备用解析成功提取身份证号码: {}", extractedIdNumber);
                    
                    // 更新字段结果
                    for (OcrRecognizeRespVO.FieldResult field : fieldResults) {
                        if ("公民身份证号码".equals(field.getFieldName())) {
                            field.setFieldValue(extractedIdNumber.trim());
                            field.setConfidence(0.8); // 备用解析的置信度较低
                            break;
                        }
                    }
                    
                    // 更新文本结果
                    String currentText = response.getText() != null ? response.getText() : "";
                    if (!currentText.contains("公民身份证号码: " + extractedIdNumber)) {
                        currentText += "\n公民身份证号码: " + extractedIdNumber + " (备用解析)";
                        response.setText(currentText.trim());
                    }
                }
            }
        } catch (Exception e) {
            log.warn("备用身份证号码提取失败: {}", e.getMessage());
        }
    }
    
    /**
     * 从 OCR 结果中提取全文本内容
     */
    private String extractFullTextFromResult(JSONObject result) {
        StringBuilder fullText = new StringBuilder();
        
        try {
            if (result.has("words_result")) {
                Object wordsResultObj = result.get("words_result");
                
                // 处理对象格式（身份证类型）
                if (wordsResultObj instanceof JSONObject) {
                    JSONObject wordsResult = (JSONObject) wordsResultObj;
                    for (String key : wordsResult.keySet()) {
                        JSONObject fieldObj = wordsResult.getJSONObject(key);
                        String words = fieldObj.optString("words", "");
                        if (!words.trim().isEmpty()) {
                            fullText.append(words).append(" ");
                        }
                    }
                }
                // 处理数组格式（通用文本类型）
                else if (wordsResultObj instanceof JSONArray) {
                    JSONArray wordsResult = (JSONArray) wordsResultObj;
                    for (int i = 0; i < wordsResult.length(); i++) {
                        JSONObject word = wordsResult.getJSONObject(i);
                        String words = word.optString("words", "");
                        if (!words.trim().isEmpty()) {
                            fullText.append(words).append(" ");
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.warn("提取全文本失败: {}", e.getMessage());
        }
        
        return fullText.toString().trim();
    }
    
    /**
     * 从文本中提取身份证号码
     */
    private String extractIdNumberFromText(String text) {
        if (text == null || text.trim().isEmpty()) {
            return null;
        }
        
        log.info("开始从文本提取身份证号码，文本内容: {}", text);
        
        // 多种匹配策略，逐一尝试
        String[] strategies = {
            // 策略1: 直接匹配18位身份证号码（最严格）
            "([0-9]{17}[0-9Xx])",
            // 策灥2: 匹配15位身份证号码
            "([0-9]{15})",
            // 策灥3: 带空格的身份证号码
            "([0-9\\s]{17,20}[0-9Xx\\s]?)",
            // 策灥4: 带各种分隔符的身份证号码
            "([0-9\\s\\-_]{17,25}[0-9Xx\\s]?)"
        };
        
        for (int i = 0; i < strategies.length; i++) {
            String strategy = strategies[i];
            Pattern pattern = Pattern.compile(strategy);
            
            // 先在原始文本中尝试
            Matcher matcher = pattern.matcher(text);
            if (matcher.find()) {
                String candidate = matcher.group(1);
                String cleanCandidate = candidate.replaceAll("[^0-9Xx]", "");
                
                if (isValidIdNumber(cleanCandidate)) {
                    log.info("策略{}成功提取身份证号码: {}", i + 1, cleanCandidate);
                    return cleanCandidate;
                }
            }
            
            // 再在清理后的文本中尝试
            String cleanText = text.replaceAll("[^0-9Xx\\u4e00-\\u9fa5]", "");
            Matcher cleanMatcher = pattern.matcher(cleanText);
            if (cleanMatcher.find()) {
                String candidate = cleanMatcher.group(1);
                String cleanCandidate = candidate.replaceAll("[^0-9Xx]", "");
                
                if (isValidIdNumber(cleanCandidate)) {
                    log.info("策略{}（清理后）成功提取身份证号码: {}", i + 1, cleanCandidate);
                    return cleanCandidate;
                }
            }
        }
        
        // 最后的尝试：查找所有数字和字母组合
        String numbersOnly = text.replaceAll("[^0-9Xx]", "");
        if (numbersOnly.length() >= 15) {
            // 尝试从左到右找合适的身份证号码
            for (int start = 0; start <= numbersOnly.length() - 15; start++) {
                // 先尝试18位
                if (start + 18 <= numbersOnly.length()) {
                    String candidate18 = numbersOnly.substring(start, start + 18);
                    if (isValidIdNumber(candidate18)) {
                        log.info("数字扫描策略成功提取18位身份证号码: {}", candidate18);
                        return candidate18;
                    }
                }
                // 再尝试15位
                if (start + 15 <= numbersOnly.length()) {
                    String candidate15 = numbersOnly.substring(start, start + 15);
                    if (isValidIdNumber(candidate15)) {
                        log.info("数字扫描策略成功提取15位身份证号码: {}", candidate15);
                        return candidate15;
                    }
                }
            }
        }
        
        log.warn("所有策略都失败，未能从文本中提取身份证号码，数字字符串: {}", numbersOnly);
        return null;
    }
    
    /**
     * 验证身份证号码是否有效
     */
    private boolean isValidIdNumber(String idNumber) {
        if (idNumber == null || idNumber.trim().isEmpty()) {
            return false;
        }
        
        String trimmed = idNumber.trim().toUpperCase();
        
        // 基本格式检查
        if (trimmed.matches("[0-9]{15}")) {
            return true; // 15位身份证
        }
        
        if (trimmed.matches("[0-9]{17}[0-9X]")) {
            // 18位身份证，进行更严格的验证
            return validate18DigitIdNumber(trimmed);
        }
        
        return false;
    }
    
    /**
     * 验证18位身份证号码的有效性
     */
    private boolean validate18DigitIdNumber(String idNumber) {
        if (idNumber == null || idNumber.length() != 18) {
            return false;
        }
        
        // 验证前17位都是数字
        String first17 = idNumber.substring(0, 17);
        if (!first17.matches("[0-9]+")) {
            return false;
        }
        
        // 验证最后一位校验码
        char actualCheckCode = idNumber.charAt(17);
        char expectedCheckCode = calculateIdCheckCode(first17);
        
        return actualCheckCode == expectedCheckCode;
    }
    
    /**
     * 计算身份证校验码
     */
    private char calculateIdCheckCode(String first17) {
        // 权重系数
        int[] weights = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        // 校验码对应表
        char[] checkCodes = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
        
        int sum = 0;
        for (int i = 0; i < 17; i++) {
            int digit = Character.getNumericValue(first17.charAt(i));
            sum += digit * weights[i];
        }
        
        int mod = sum % 11;
        return checkCodes[mod];
    }

    /**
     * Parse special document OCR result (business license, bank card, etc.)
     */
    private void parseSpecialDocumentResult(OcrRecognizeRespVO response, JSONObject result) {
        StringBuilder textBuilder = new StringBuilder();
        List<OcrRecognizeRespVO.WordResult> wordResults = new ArrayList<>();

        // Try to parse as general format first
        if (result.has("words_result")) {
            Object wordsResultObj = result.get("words_result");

            if (wordsResultObj instanceof JSONArray) {
                // Array format (like general text)
                parseGeneralTextResult(response, result);
                return;
            } else if (wordsResultObj instanceof JSONObject) {
                // Object format (like ID card)
                JSONObject wordsResult = (JSONObject) wordsResultObj;
                for (String key : wordsResult.keySet()) {
                    JSONObject fieldObj = wordsResult.getJSONObject(key);
                    String fieldText = fieldObj.optString("words", "");
                    if (!fieldText.isEmpty()) {
                        textBuilder.append(key).append(": ").append(fieldText).append("\n");

                        // Create word result
                        OcrRecognizeRespVO.WordResult wordResult = new OcrRecognizeRespVO.WordResult();
                        wordResult.setText(key + ": " + fieldText);
                        wordResult.setConfidence(fieldObj.optDouble("probability", 0.0));
                        wordResults.add(wordResult);
                    }
                }
            }
        }

        response.setText(textBuilder.toString().trim());
        response.setConfidence(0.90); // Default confidence for special documents
        response.setWords(wordResults);
    }

    /**
     * Reads all bytes from an input stream.
     * This is a replacement for InputStream.readAllBytes() which is only available in Java 9+.
     *
     * @param inputStream the input stream to read from
     * @return a byte array containing all the bytes from the input stream
     * @throws IOException if an I/O error occurs
     */
    private byte[] readAllBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[1024];
        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        return buffer.toByteArray();
    }

    // ========== API methods (for Feign calls) ==========

    @Override
    public OcrRecognizeRespDTO recognizeIdCardForApi(Long configId, Long fileId) {
        OcrRecognizeRespVO vo = recognizeIdCard(configId, fileId);
        return convertToApiFormat(vo);
    }

    @Override
    public OcrRecognizeRespDTO recognizeDrivingLicenseForApi(Long configId, Long fileId) {
        OcrRecognizeRespVO vo = recognizeDrivingLicense(configId, fileId);
        return convertToApiFormat(vo);
    }

    @Override
    public OcrRecognizeRespDTO recognizeTextForApi(Long configId, Long fileId) {
        OcrRecognizeRespVO vo = recognizeText(configId, fileId);
        return convertToApiFormat(vo);
    }

    /**
     * Convert VO to DTO format for API responses
     */
    private OcrRecognizeRespDTO convertToApiFormat(OcrRecognizeRespVO vo) {
        OcrRecognizeRespDTO dto = new OcrRecognizeRespDTO();
        dto.setRecordId(vo.getRecordId());
        dto.setStatus(vo.getStatus());
        dto.setText(vo.getText());
        dto.setConfidence(vo.getConfidence());
        dto.setErrorMsg(vo.getErrorMsg());
        dto.setProcessTime(vo.getProcessTime());

        // Convert fields
        if (vo.getFields() != null) {
            List<OcrRecognizeRespDTO.OcrFieldResult> dtoFields = new ArrayList<>();
            for (OcrRecognizeRespVO.FieldResult voField : vo.getFields()) {
                OcrRecognizeRespDTO.OcrFieldResult dtoField = new OcrRecognizeRespDTO.OcrFieldResult();
                dtoField.setFieldName(voField.getFieldName());
                dtoField.setFieldValue(voField.getFieldValue());
                dtoField.setConfidence(voField.getConfidence());
                dtoFields.add(dtoField);
            }
            dto.setFields(dtoFields);
        }

        // Convert words
        if (vo.getWords() != null) {
            List<OcrRecognizeRespDTO.WordResult> dtoWords = new ArrayList<>();
            for (OcrRecognizeRespVO.WordResult voWord : vo.getWords()) {
                OcrRecognizeRespDTO.WordResult dtoWord = new OcrRecognizeRespDTO.WordResult();
                dtoWord.setText(voWord.getText());
                dtoWord.setConfidence(voWord.getConfidence());
                
                if (voWord.getLocation() != null) {
                    OcrRecognizeRespDTO.Location dtoLocation = new OcrRecognizeRespDTO.Location();
                    dtoLocation.setLeft(voWord.getLocation().getLeft());
                    dtoLocation.setTop(voWord.getLocation().getTop());
                    dtoLocation.setWidth(voWord.getLocation().getWidth());
                    dtoLocation.setHeight(voWord.getLocation().getHeight());
                    dtoWord.setLocation(dtoLocation);
                }
                
                dtoWords.add(dtoWord);
            }
            dto.setWords(dtoWords);
        }

        return dto;
    }
}